package cn.uncode.session;

import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletContext;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;

import cn.uncode.session.data.SessionCacheManager;
import cn.uncode.session.data.SessionMap;

public class SessionHttpServletRequestWrapper extends HttpServletRequestWrapper {
	
	public static final String CURRENT_SESSION_ATTR = SessionHttpServletRequestWrapper.class.getName();
	
	public static final String USER_IDENTITY_INFO_KEY = "_user_identity_info"; 
	
	private ServletContext servletContext;

	private String tokenName;

	private  String body;
	

	public SessionHttpServletRequestWrapper(HttpServletRequest request, ServletContext servletContext, String tokenName) throws IOException{
		super(request);
		this.servletContext = servletContext;
		this.tokenName = tokenName;
		initBody(request);
	}
	
	@Override
	public HttpSession getSession() {
		return getSession(true);
	}
	
    @Override
    public HttpSession getSession(boolean create) {
    	
    	String userIdentiryInfo = getUserIdentityInfo();
    	
    	HttpSessionWrapper currentSession = getCurrentSession();
		if(currentSession != null) {
			return currentSession;
		}
    	
    	String sessionId = getTokenId(this);

		if(sessionId != null) {
			SessionMap sessionMap = SessionCacheManager.getSessionCache().get(sessionId);
			if(sessionMap != null && sessionMap.isInvalidated() == false) {
				SessionCacheManager.getSessionCache().setAdd(userIdentiryInfo, sessionId);
				currentSession = new HttpSessionWrapper(sessionMap, SessionCacheManager.getSessionCache(), servletContext, userIdentiryInfo);
				currentSession.setNew(false);
				setCurrentSession(currentSession);
				syncSessionInfo(userIdentiryInfo, currentSession);
				return currentSession;
			}
		}
		
		if(!create) {
			return null;
		}
		HttpSession httpSession = super.getSession();
		SessionMap sessionMap = new SessionMap(httpSession);
		if(StringUtils.isNotBlank(sessionId)){
			SessionCacheManager.getSessionCache().setAdd(userIdentiryInfo, sessionId);
		}
		sessionMap.setAttribute(USER_IDENTITY_INFO_KEY, userIdentiryInfo);
		int sessionTimeOut = httpSession.getMaxInactiveInterval();
		SessionCacheManager.getSessionCache().put(sessionMap.getId(), sessionMap, sessionTimeOut);
		
		currentSession = new HttpSessionWrapper(sessionMap, SessionCacheManager.getSessionCache(), servletContext, userIdentiryInfo);
		setCurrentSession(currentSession);
		syncSessionInfo(userIdentiryInfo, currentSession);
		return currentSession;
    }
	@Override
    public boolean isRequestedSessionIdValid() {
    	HttpSessionWrapper httpSessionWrapper = (HttpSessionWrapper) this.getSession(false);
    	if(httpSessionWrapper == null){
    		return false;
    	}
    	boolean rt = httpSessionWrapper.isInvalidated();
    	if(!rt){
    		String redisInfo = (String) httpSessionWrapper.getAttribute(USER_IDENTITY_INFO_KEY);
    		String userInfo = getUserIdentityInfo();
    		if(StringUtils.isBlank(redisInfo) || !redisInfo.equals(userInfo)){
    			return false;
    		}else{
    		    return true;
            }
    	}
		return false;
    }
	
    /**
     * 同步session信息
     * @param userIdentiryInfo 用户身份标识
     * @param currentSession 当前session
     */
    private void syncSessionInfo(String userIdentiryInfo, HttpSessionWrapper currentSession) {
		if(StringUtils.isNotBlank(tokenName)){
			String token = getTokenId(this);
			if(StringUtils.isNotBlank(token)){
				SessionMap redisSessionMap = SessionCacheManager.getSessionCache().get(token);
				if(null != redisSessionMap && redisSessionMap.isInvalidated() == false){
					for(Map.Entry<String, Object> item:redisSessionMap.getSessionAttrs().entrySet()){
						currentSession.setAttribute(item.getKey(), item.getValue());
					}
				}
                SessionCacheManager.getSessionCache().setAdd(userIdentiryInfo, token);
			}
		}
	}
    
    /**
     * 读取当前本地session
     * @return
     */
	private HttpSessionWrapper getCurrentSession() {
		return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
	}

    /**
     * 设置当前本地session
     * @return
     */
	private void setCurrentSession(HttpSessionWrapper currentSession) {
		if(currentSession == null) {
			removeAttribute(CURRENT_SESSION_ATTR);
		} else {
			setAttribute(CURRENT_SESSION_ATTR, currentSession);
		}
	}
	
	/**
	 * 获取用户身份标识
	 * @return
	 */
    private String getUserIdentityInfo(){
    	HttpServletRequest request = (HttpServletRequest) getRequest();
		StringBuilder sb = new StringBuilder();
		sb.append(getIpAddr(request).replaceAll("\\s*", ""));
		//sb.append(request.getRemoteHost().replaceAll("\\s*", ""));
		sb.append(request.getProtocol().replaceAll("\\s*", ""));
		String userAgent = request.getHeader("user-Agent");
		if(null!=userAgent){
			sb.append(userAgent.replaceAll("\\s*", ""));
		}
	  //只保留applicationCode和clientId的指纹信息
        String header = request.getHeader("header");
        if (null != header) {
            JsonElement jsonEle = new JsonParser().parse(header.replaceAll("\\s*", ""));
            JsonObject jsonObj = jsonEle.getAsJsonObject();
            Iterator<Map.Entry<String, JsonElement>> iterator = jsonObj.entrySet().iterator();
            iterator.forEachRemaining(item -> {
                String key = item.getKey();
                if (!"applicationCode".equals(key) && !"clientId".equals(key)) {
                    item.setValue(JsonNull.INSTANCE);
                }
            });
            sb.append(jsonObj.toString());
        }
        return sb.toString();
    }
	/**
	 * 获得真实ip
	 * @param request
	 * @author lijianjun
	 * @date 2018/7/20 14:51
	 * @return
	 */
	private String getIpAddr(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		if(null!=ip && !"unknown".equalsIgnoreCase(ip) && ip.indexOf(",")>0){
			String[] ips=ip.split(",");
			for(String cip:ips){
				if(!"unknown".equalsIgnoreCase(cip)){
					return cip;
				}
			}
		}
		return ip;
	}

	private void initBody(HttpServletRequest request) throws IOException {
		int contentLength = request.getContentLength();
		if(contentLength<0) {
			return;
		}
		StringBuilder stringBuilder = new StringBuilder();
		BufferedReader bufferedReader = null;
		try {
			InputStream inputStream = request.getInputStream();
			if (inputStream != null) {
				bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
				char[] charBuffer = new char[contentLength];
				int bytesRead = -1;
				while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
					stringBuilder.append(charBuffer, 0, bytesRead);
				}
			} else {
				stringBuilder.append("");
			}
		} catch (IOException ex) {
			throw ex;
		} finally {
			if (bufferedReader != null) {
				try {
					bufferedReader.close();
				} catch (IOException ex) {
					throw ex;
				}
			}
		}
		body = stringBuilder.toString();
	}

	@Override
	public ServletInputStream getInputStream() throws IOException {
		if(StringUtils.isBlank(body)){
			body="";
		}
		final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
		ServletInputStream servletInputStream = new ServletInputStream() {
			@Override
			public int read() throws IOException {
				return byteArrayInputStream.read();
			}
		};
		return servletInputStream;
	}

	@Override
	public BufferedReader getReader() throws IOException {
		return new BufferedReader(new InputStreamReader(this.getInputStream()));
	}
	
	private String getTokenId(HttpServletRequest request) {
		String sessionId = getTokenIdFromCookie(request);
    	if(null == sessionId) {
    		sessionId = getTokenIdFromHeader(request);
    	}
    	if(null == sessionId) {
    		sessionId = getTokenIdFromParameter(request);
    	}
    	return sessionId;
	}
	
	private String getTokenIdFromCookie(HttpServletRequest request) {
		String sessionId = null;
    	Cookie[] cookies = request.getCookies();

    	if(cookies != null){
    		for(Cookie cookie:cookies){
    			if(tokenName.trim().toLowerCase().equals(cookie.getName().toLowerCase())){
    				sessionId = cookie.getValue();
    				break;
    			}
    		}
    	}
		return sessionId;
	}
	
	private String getTokenIdFromHeader(HttpServletRequest request) {
		String sessionId = null;
		String auth = request.getHeader(tokenName);
		if ((auth != null) && (auth.length() > 1)) {
			sessionId = getTokenFromJsonString(auth);
		}
		return sessionId;
	}
	
	private String getTokenIdFromParameter(HttpServletRequest request) {
		return request.getParameter(tokenName);
	}
	
	private String getTokenFromJsonString(String jsonString) {
		if(StringUtils.isBlank(jsonString)) {
			return null;
		}
		String value = null;
		String regex = "\"" + tokenName.toLowerCase() + "\":\"(.*?)\\\"";
		jsonString = jsonString.toLowerCase();
		Matcher matcher = Pattern.compile(regex).matcher(jsonString.toLowerCase());
		while (matcher.find()) {
			value = matcher.group(1);
			break;
		}
		if(StringUtils.isNotBlank(jsonString)) {
			return value.toUpperCase();
		}
		return value;
	}

}
